package org.jacp.demo.io;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;


public final class ExifIFD {
    private static final Hashtable<Integer, String> LOOKUPTABLE = new Hashtable<Integer, String>();
    static {
	LOOKUPTABLE.put(Integer.valueOf(256), "Image Width");
	LOOKUPTABLE.put(Integer.valueOf(257), "Image Length");
	LOOKUPTABLE.put(Integer.valueOf(258), "BitsPerSample");
	LOOKUPTABLE.put(Integer.valueOf(259), "Compression");
	LOOKUPTABLE.put(Integer.valueOf(262), "PhotometricInterpretation");
	LOOKUPTABLE.put(Integer.valueOf(270), "ImageDescription");
	LOOKUPTABLE.put(Integer.valueOf(271), "Make");
	LOOKUPTABLE.put(Integer.valueOf(272), "Model");
	LOOKUPTABLE.put(Integer.valueOf(273), "StripOffsets");
	LOOKUPTABLE.put(Integer.valueOf(274), "Orientation");
	LOOKUPTABLE.put(Integer.valueOf(277), "SamplesPerPixel");
	LOOKUPTABLE.put(Integer.valueOf(278), "RowsPerStrip");
	LOOKUPTABLE.put(Integer.valueOf(279), "StripByteCounts");
	LOOKUPTABLE.put(Integer.valueOf(282), "XResolution");
	LOOKUPTABLE.put(Integer.valueOf(283), "YResolution");
	LOOKUPTABLE.put(Integer.valueOf(284), "PlanarConfiguration");
	LOOKUPTABLE.put(Integer.valueOf(296), "ResolutionUnit");
	LOOKUPTABLE.put(Integer.valueOf(301), "TranserFunction");
	LOOKUPTABLE.put(Integer.valueOf(305), "Software");
	LOOKUPTABLE.put(Integer.valueOf(306), "DateTime");
	LOOKUPTABLE.put(Integer.valueOf(315), "Artist");
	LOOKUPTABLE.put(Integer.valueOf(318), "WhitePoint");
	LOOKUPTABLE.put(Integer.valueOf(319), "PrimaryChromaticities");
	LOOKUPTABLE.put(Integer.valueOf(513), "JPEGInterchangeFormat");
	LOOKUPTABLE.put(Integer.valueOf(514), "JPEGInterchangeFormatLength");
	LOOKUPTABLE.put(Integer.valueOf(529), "YCbCrCoefficients");
	LOOKUPTABLE.put(Integer.valueOf(530), "YCbCrSubSampling");
	LOOKUPTABLE.put(Integer.valueOf(531), "YCbCrPositioning");
	LOOKUPTABLE.put(Integer.valueOf(532), "ReferenceBlackWhite");
	LOOKUPTABLE.put(Integer.valueOf(33432), "Copyright");
	LOOKUPTABLE.put(Integer.valueOf(34665), "Exif IFD Pointer");
	LOOKUPTABLE.put(Integer.valueOf(34853), "GPSInfo IFD Pointer");
	LOOKUPTABLE.put(Integer.valueOf(33434), "ExposureTime");
	LOOKUPTABLE.put(Integer.valueOf(33437), "FNumber");
	LOOKUPTABLE.put(Integer.valueOf(34850), "ExposureProgram");
	LOOKUPTABLE.put(Integer.valueOf(34852), "SpectralSensitivity");
	LOOKUPTABLE.put(Integer.valueOf(34855), "ISOSpeedRatings");
	LOOKUPTABLE.put(Integer.valueOf(34856), "OECF");
	LOOKUPTABLE.put(Integer.valueOf(36864), "ExifVersion");
	LOOKUPTABLE.put(Integer.valueOf(36867), "DateTimeOriginal");
	LOOKUPTABLE.put(Integer.valueOf(36868), "DateTimeDigitized");
	LOOKUPTABLE.put(Integer.valueOf(37121), "ComponentsConfiguration");
	LOOKUPTABLE.put(Integer.valueOf(37122), "CompressedBitsPerPixel");
	LOOKUPTABLE.put(Integer.valueOf(37377), "ShutterSpeedValue");
	LOOKUPTABLE.put(Integer.valueOf(37378), "ApertureValue");
	LOOKUPTABLE.put(Integer.valueOf(37379), "BrightnessValue");
	LOOKUPTABLE.put(Integer.valueOf(37380), "ExposureBiasValue");
	LOOKUPTABLE.put(Integer.valueOf(37381), "MaxApertureValue");
	LOOKUPTABLE.put(Integer.valueOf(37382), "SubjectDistance");
	LOOKUPTABLE.put(Integer.valueOf(37383), "MeteringMode");
	LOOKUPTABLE.put(Integer.valueOf(37384), "LightSource");
	LOOKUPTABLE.put(Integer.valueOf(37385), "Flash");
	LOOKUPTABLE.put(Integer.valueOf(37386), "FocalLength");
	LOOKUPTABLE.put(Integer.valueOf(37396), "SubjectArea");
	LOOKUPTABLE.put(Integer.valueOf(37500), "MakerNote");
	LOOKUPTABLE.put(Integer.valueOf(37510), "UserComment");
	LOOKUPTABLE.put(Integer.valueOf(37520), "SubSecTime");
	LOOKUPTABLE.put(Integer.valueOf(37521), "SubSecTimeOriginal");
	LOOKUPTABLE.put(Integer.valueOf(37522), "SubSecTimeDigitized");

	LOOKUPTABLE.put(Integer.valueOf(40960), "FlashpixVersion");
	LOOKUPTABLE.put(Integer.valueOf(40961), "ColorSpace");
	LOOKUPTABLE.put(Integer.valueOf(40962), "PixelXDimension");
	LOOKUPTABLE.put(Integer.valueOf(40963), "PixelYDimension");
	LOOKUPTABLE.put(Integer.valueOf(40964), "RelatedSoundFile");
	LOOKUPTABLE.put(Integer.valueOf(40965), "Interoperability IFD Pointer");
	LOOKUPTABLE.put(Integer.valueOf(41483), "FlashEnergy");
	LOOKUPTABLE.put(Integer.valueOf(41484), "SpatialFrequencyResponse");
	LOOKUPTABLE.put(Integer.valueOf(41486), "FocalPlaneXResolution");
	LOOKUPTABLE.put(Integer.valueOf(41487), "FocalPlaneYResolution");
	LOOKUPTABLE.put(Integer.valueOf(41488), "FocalPlaneResolutionUnit");
	LOOKUPTABLE.put(Integer.valueOf(41492), "SubjectLocation");
	LOOKUPTABLE.put(Integer.valueOf(41493), "ExposureIndex");
	LOOKUPTABLE.put(Integer.valueOf(41495), "SensingMethod");
	LOOKUPTABLE.put(Integer.valueOf(41728), "FileSource");
	LOOKUPTABLE.put(Integer.valueOf(41729), "SceneType");
	LOOKUPTABLE.put(Integer.valueOf(41730), "CFAPattern");
	LOOKUPTABLE.put(Integer.valueOf(41985), "CustomRendered");
	LOOKUPTABLE.put(Integer.valueOf(41986), "ExposureMode");
	LOOKUPTABLE.put(Integer.valueOf(41987), "WhiteBalance");
	LOOKUPTABLE.put(Integer.valueOf(41988), "DigitalZoomRatio");
	LOOKUPTABLE.put(Integer.valueOf(41989), "FocalLengthIn35mmFilm");
	LOOKUPTABLE.put(Integer.valueOf(41990), "SceneCaptureType");
	LOOKUPTABLE.put(Integer.valueOf(41991), "GainControl");
	LOOKUPTABLE.put(Integer.valueOf(41992), "Contrast");
	LOOKUPTABLE.put(Integer.valueOf(41993), "Saturation");
	LOOKUPTABLE.put(Integer.valueOf(41994), "Sharpness");
	LOOKUPTABLE.put(Integer.valueOf(41995), "DeviceSettingDescription");
	LOOKUPTABLE.put(Integer.valueOf(41996), "SubjectDistanceRange");
	LOOKUPTABLE.put(Integer.valueOf(42016), "ImageUniqueID");
    }
    private final List<byte[][]> v = new CopyOnWriteArrayList<byte[][]>();
    private final int size;
    private ExifIFD exifIFDPtr;

    public ExifIFD(final InputStream in, final boolean reverse)
	    throws IOException {
	final byte[] sizeArray = new byte[2];
	read(in, sizeArray, 2, reverse);
	size = (sizeArray[0] & 0xff) * 256 + (sizeArray[1] & 0xff);
	for (int a = 0; a < size; a++) {
	    final byte[] b1 = new byte[2];
	    read(in, b1, 2, reverse);
	    final long k = evaluate(b1);

	    final boolean relatedToThumbnail = k == 513 || k == 514;

	    if (relatedToThumbnail) {
		final byte[] b2 = new byte[2];
		final byte[] b3 = new byte[4];
		final byte[] b4 = new byte[4];
		read(in, b2, 2, reverse);
		read(in, b3, 4, reverse);
		read(in, b4, 4, reverse);
		final byte[][] b = new byte[][] { b1, b2, b3, b4 };
		v.add(b);
	    } else {
		in.skip(10);
	    }
	}
    }

    public void resolveIFDs(final MeasuredInputStream in,
	    final boolean reverse, final int offset) throws IOException {
	for (int a = 0; a < v.size(); a++) {
	    final byte[][] b = v.get(a);
	    final long k = evaluate(b[0]);
	    if (k == 34665) {
		in.seek(evaluate(b[3]) + offset);
		exifIFDPtr = new ExifIFD(in, reverse);
	    }
	}
	if (exifIFDPtr != null) {
	    exifIFDPtr.resolveIFDs(in, reverse, offset);
	}
    }

    public long getJPEGPointer() {
	for (int a = 0; a < v.size(); a++) {
	    final byte[][] b = v.get(a);
	    if (evaluate(b[0]) == 513) {
		return evaluate(b[3]);
	    }
	}
	return -1;
    }

    public long getJPEGLength() {
	for (int a = 0; a < v.size(); a++) {
	    final byte[][] b = v.get(a);
	    if (evaluate(b[0]) == 514) {
		return evaluate(b[3]);
	    }
	}
	return -1;
    }

    private final long evaluate(final byte[] b) {
	long sum = 0;
	for (int a = 0; a < b.length; a++) {
	    sum = sum + (b[a] & 0xff);
	    if (a != b.length - 1) {
		sum = sum * 256;
	    }
	}
	return sum;
    }

    private final void read(final InputStream in, final byte[] dest,
	    final int amt, final boolean reverse) throws IOException {
	int ctr = 0;
	int t = in.read(dest, ctr, amt - ctr);
	while (t + ctr != amt && t != -1) {
	    ctr += t;
	    t = in.read(dest, ctr, amt - ctr);
	}
	if (t != -1) {
	    ctr += t;
	}

	if (ctr != amt) {
	    throw new EOFException();
	}

	if (reverse) {
	    t = amt / 2;
	    for (int i = 0; i < t; i++) {
		final byte k = dest[amt - 1 - i];
		dest[amt - 1 - i] = dest[i];
		dest[i] = k;
	    }
	}
    }
}
